package pl.ydp.components.mathcell.mathrecognizer.model
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Angle;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Primitive;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.PrimitiveStructure;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.FeaturesCross;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.AngleDescriptor;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.ConnectionsDescriptor;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.CrossDescriptior;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.DistanceDescriptor;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.LocationDescriptor;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.ObjectDescriptor;
	import pl.ydp.components.mathcell.mathrecognizer.model.descriptors.SizeDescriptor;
	
	public class PrimitivesRelation
	{
		public function PrimitivesRelation(_type:Number, _param:Number,  _weight:Number = 10, _refObject:Number = 1, _testObject:Number = 1){
			refObject = _refObject;
			testObject = _testObject;
			type = _type;
			param = _param;
			weight = _weight; 
		} 
		
		private var refObject:Number;
		private var testObject:Number;
		private var type:Number;
		private var param:Number;
		public var weight:Number; 
		
			

		//  -------------------- PUBLIC METHODS -------------------- 
		
		public function check(ref:PrimitiveStructure, test:PrimitiveStructure):Number{
			
			var result:Boolean;
			
			if (type == RelationDescriptorType.LOCATION){
				result = checkLocation(ref, test);
			} else if (type == RelationDescriptorType.CONNECTION){
				result = checkConnections(ref, test);
			} else if (type == RelationDescriptorType.CROSS){
				result = checkCross(ref, test);
			} else if (type == RelationDescriptorType.SIZE){
				result = checkSize(ref, test);
			} else if (type == RelationDescriptorType.ANGLE){
				result = checkAngle(ref, test);
			}  else if (type == RelationDescriptorType.DISTANCE){
				result = checkDistance(ref, test);
			}  else {
				result = false;
			}
			
			if (result){
				return weight;
			}
			
			return 0;
		}
		

		// -------------------- PRIVATE METHODS -------------------- 

		private function findObjectPoint(primitive:PrimitiveStructure, objectDescr:Number):Point{
			
			var point:Point;
			var genuine:Primitive = primitive as Primitive;
							
			if (objectDescr == ObjectDescriptor.DEFAULT  ||  objectDescr == ObjectDescriptor.COG){
				point = primitive.cog.clone();
			} else if (objectDescr == ObjectDescriptor.BEGIN  &&  genuine != null){
				point = genuine.begin.clone();
			} else if (objectDescr == ObjectDescriptor.END  &&  genuine != null){
				point = genuine.end.clone();
				
			} else if (objectDescr == ObjectDescriptor.MAX_LEFT){
				point = new Point(primitive.field.left, primitive.cog.y);
			} else if (objectDescr == ObjectDescriptor.MAX_RIGHT){
				point = new Point(primitive.field.right, primitive.cog.y);
			} else if (objectDescr == ObjectDescriptor.MAX_BOTTOM){
				point = new Point(primitive.cog.x, primitive.field.bottom);
			} else if (objectDescr == ObjectDescriptor.MAX_TOP){
				point = new Point(primitive.cog.x, primitive.field.top);
					
			} else if (objectDescr == ObjectDescriptor.HALF_LEFT){
				point = new Point((primitive.field.left+primitive.cog.x)/2, primitive.cog.y);
			} else if (objectDescr == ObjectDescriptor.HALF_RIGHT){
				point = new Point((primitive.field.right+primitive.cog.x)/2, primitive.cog.y);
			} else if (objectDescr == ObjectDescriptor.HALF_BOTTOM){
				point = new Point(primitive.cog.x, (primitive.field.bottom+primitive.cog.y)/2);
			} else if (objectDescr == ObjectDescriptor.HALF_TOP){
				point = new Point(primitive.cog.x, (primitive.field.top+primitive.cog.y)/2);	
				
			} else if (objectDescr == ObjectDescriptor.END_WESTERN  &&  genuine != null){
				
				if (genuine.begin.x < genuine.end.x){
					point = genuine.begin.clone();	
				} else {
					point = genuine.end.clone();	
				}
				
			} else if (objectDescr == ObjectDescriptor.END_EASTERN  &&  genuine != null){
				
				if (genuine.begin.x > genuine.end.x){
					point = genuine.begin.clone();	
				} else {
					point = genuine.end.clone();	
				}
				
			} else if (objectDescr == ObjectDescriptor.END_NORTHERN  &&  genuine != null){
				
				if (genuine.begin.y < genuine.end.y){
					point = genuine.begin.clone();	
				} else {
					point = genuine.end.clone();	
				}
				
			} else if (objectDescr == ObjectDescriptor.END_SOUTHERN  &&  genuine != null){
				
				if (genuine.begin.y > genuine.end.y){
					point = genuine.begin.clone();	
				} else {
					point = genuine.end.clone();	
				}
				
			} else {
				point = primitive.cog.clone();
			}
		
			return point;
			
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkLocation(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{
			
			var location:Number = param;
			
			var result:Boolean = true;
			var refPoint:Point;
			var testPoint:Point;
			
			if (location & LocationDescriptor.UNKNOWN){
				return true;
			}
			
			// find tested points
			
			refPoint = findObjectPoint(ref, refObject);
			testPoint = findObjectPoint(test, testObject);
			
			// LOCATION
			if (location & LocationDescriptor.LEFT){
				result = (refPoint.x <= testPoint.x);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.RIGHT){
				result = (refPoint.x > testPoint.x);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.ABOVE){
				result = (refPoint.y <= testPoint.y);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.BELOW){
				result = (refPoint.y > testPoint.y);
				if (!result) { return false; }
			}
			
			
			if (location & LocationDescriptor.IN_LEFT_HALF){
				result = (refPoint.x > test.field.x  &&  refPoint.x < test.field.x + test.field.width/2);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.IN_RIGHT_HALF){
				result = (refPoint.x > test.field.x + test.field.width/2  &&  refPoint.x < test.field.x + test.field.width);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.IN_UPPER_HALF){
				result = (refPoint.y > test.field.y  &&  refPoint.y < test.field.y + test.field.height/2);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.IN_LOWER_HALF){
				result = (refPoint.y > test.field.y + test.field.height/2  &&  refPoint.y < test.field.y + test.field.height);
				if (!result) { return false; }
			}
			
			
			if (location & LocationDescriptor.INSIDE){
				result = (refPoint.x >= test.field.x  &&  refPoint.x <= test.field.right &&  
						  refPoint.y >= test.field.y  &&  refPoint.y <= test.field.bottom);
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.INSIDE_WHOLE){
				result = (ref.field.x >= test.field.x  &&  ref.field.right <= test.field.right  &&  
						  ref.field.y >= test.field.y  &&  ref.field.bottom <= test.field.bottom);
				if (!result) { return false; }
			}
			
			
			if (location & LocationDescriptor.OUTSIDE){
				result = ((refPoint.x < test.field.x  ||  refPoint.x > test.field.right) ||  
						  (refPoint.y < test.field.y  ||  refPoint.y > test.field.bottom));
				if (!result) { return false; }
			}
			if (location & LocationDescriptor.OUTSIDE_WHOLE){
				result = (ref.field.x >= test.field.right  ||  ref.field.right <= test.field.x  ||  
						  ref.field.y >= test.field.bottom  ||  ref.field.bottom <= test.field.y);
				if (!result) { return false; }
			}
			
			
			if (location & LocationDescriptor.UNKNOWN){
				result = true;
			}
			
			return result;
			
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkConnections(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{

			var connections:Number = param;

			if (connections == ConnectionsDescriptor.UNKNOWN){
				return true;
			}
			
			var refGenuine:Primitive = ref as Primitive;
			var testGenuine:Primitive = test as Primitive;
			
			if (refGenuine == null  ||  testGenuine == null){
				// the relation description is wrong
				return true;
			}
			
			
			if (connections == ConnectionsDescriptor.NONE){
				
				var d1:Number = Point.distance(refGenuine.begin, testGenuine.begin);
				var d2:Number = Point.distance(refGenuine.begin, testGenuine.end);
				var d3:Number = Point.distance(refGenuine.end, testGenuine.begin);
				var d4:Number = Point.distance(refGenuine.end, testGenuine.end);
				
				return  (
						(d1 > Parameters.MAX_CONNECTION_DISTANCE)  && 
						(d2 > Parameters.MAX_CONNECTION_DISTANCE)  && 
						(d3 > Parameters.MAX_CONNECTION_DISTANCE)  && 
						(d4 > Parameters.MAX_CONNECTION_DISTANCE)
						)
				
			} else if (connections == ConnectionsDescriptor.EXIST  ||  connections == ConnectionsDescriptor.EXIST_CLOSE){
				
				var refPoint:Point;
				var testPoint:Point;
				
				var CONNECTION_DISTANCE_MAX:Number;
				
				if (connections == ConnectionsDescriptor.EXIST_CLOSE){
					CONNECTION_DISTANCE_MAX = Parameters.MAX_CONNECTION_DISTANCE / 2;
				} else {
					CONNECTION_DISTANCE_MAX = Parameters.MAX_CONNECTION_DISTANCE;
				}
				
				var v:Number;
				var ref_v:Number,test_v:Number;
				
				// find tested points
				
				if (testObject == ObjectDescriptor.BODY  &&  refObject == ObjectDescriptor.BODY){
					
					for (ref_v = 0 ; ref_v < refGenuine.vectors.length ; ref_v += Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2 ){
						for (test_v = 0 ; test_v < testGenuine.vectors.length ; test_v += Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2 ){
							
							if (Point.distance(refGenuine.vectors[ref_v].point, testGenuine.vectors[test_v].point) < CONNECTION_DISTANCE_MAX){
								return true;
							}
							
						}
					}	
					
					return false;
					
				} else if (refObject == ObjectDescriptor.BODY){
					
					testPoint = findObjectPoint(test, testObject);
					
					for (v = 0 ; v < refGenuine.vectors.length ; v += Math.round(Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2) ){
						if (Point.distance(testPoint, refGenuine.vectors[v].point) < CONNECTION_DISTANCE_MAX){
							return true;
						}
					}	
					return false;
						
				} else if (testObject == ObjectDescriptor.BODY){
					
					refPoint = findObjectPoint(ref, refObject);
					
					for (v = 0 ; v < testGenuine.vectors.length ; v += Math.round(Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2) ){
						if (Point.distance(refPoint, testGenuine.vectors[v].point) < CONNECTION_DISTANCE_MAX){
							return true;
						}
					}	
					return false;
					
				} else {
					
					refPoint = findObjectPoint(ref, refObject);
					testPoint = findObjectPoint(test, testObject);
					
				}
				
				
				return (Point.distance(refPoint, testPoint) < CONNECTION_DISTANCE_MAX);
				
			}
			
			
			return true;
			
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkCross(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{
			
			var cross:Number = param;
			
			if (cross == CrossDescriptior.UNKNOWN){
				return true;
			}
			
			var isCrossing:Boolean = false;
			var result:Boolean = true;
			
			if (!ref.field.intersects(test.field)   &&   cross == CrossDescriptior.CROSSING){
				return false;
			}
			
			var refGenuine:Primitive = ref as Primitive;
			var testGenuine:Primitive = test as Primitive;
			
			if (refGenuine == null  ||  testGenuine == null){
				// the relation description is wrong
				return true;
			}
			
			if ((refGenuine.type & PrimitiveType.DOT)  ||  (testGenuine.type & PrimitiveType.DOT)){
				// DOTs cannot intersect
				return true;
			}
		
			if (((refGenuine.type & PrimitiveType.CURVE)  ||  (testGenuine.type & PrimitiveType.CURVE))  && 
				((refGenuine.type & PrimitiveType.LINE)  ||  (testGenuine.type & PrimitiveType.LINE))){
				
				var curvePrimitive:Primitive;
				var linePrimitive:Primitive;
				
				if (refGenuine.type & PrimitiveType.CURVE){
					curvePrimitive = refGenuine;
					linePrimitive = testGenuine;
				}else{
					curvePrimitive = testGenuine;
					linePrimitive = refGenuine;
				}
				
				var tmpResult:Boolean = false;
				
				for (var ic:Number = 0  ; ic < curvePrimitive.vectors.length - Parameters.MIN_LINE_LENGTH ; ic += Parameters.MIN_LINE_LENGTH ){
					var endIndex:Number = ic + Parameters.MIN_LINE_LENGTH;
					if (endIndex >= curvePrimitive.vectors.length)
						endIndex = curvePrimitive.vectors.length;
					tmpResult = FeaturesCross.findLinesCrossing(linePrimitive.begin, linePrimitive.end, curvePrimitive.vectors[ic].point, curvePrimitive.vectors[endIndex].point);
					if (tmpResult){
						isCrossing = true;
						break;
					}
				}
			} else if ((refGenuine.type & PrimitiveType.CURVE)  &&  (testGenuine.type & PrimitiveType.CURVE)){
				
				// TODO
				
			} else if ((refGenuine.type & PrimitiveType.LINE)  &&  (testGenuine.type & PrimitiveType.LINE)) {
				/*
				var v1:EuclideanVector = new EuclideanVector(refGenuine.begin, refGenuine.end);
				var v2:EuclideanVector = new EuclideanVector(testGenuine.begin, testGenuine.end);
				
				var refA:Number = (refGenuine.begin.y - refGenuine.end.y) / (refGenuine.begin.x - refGenuine.end.x);
				var refB:Number = refGenuine.begin.y - refA * refGenuine.begin.x;
				
				var testA:Number = (testGenuine.begin.y - testGenuine.end.y) / (testGenuine.begin.x - testGenuine.end.x);
				var testB:Number = testGenuine.begin.y - testA * testGenuine.begin.x;
				
				var X:Number = -1 * (testB - refB) / (testA - refA);
				var Y:Number = refA * X + refB;
				
				if (((X > refGenuine.begin.x  &&  X < refGenuine.end.x) ||  Math.abs(refA) > 16)  &&
					((Y > refGenuine.begin.y  &&  Y < refGenuine.end.y) ||  Math.abs(refB) < 1/16)   && 
					((X > testGenuine.begin.x  &&  X < testGenuine.end.x) ||  Math.abs(testA) > 16)   &&
					((Y > testGenuine.begin.y  &&  Y < testGenuine.end.y) ||  Math.abs(testB) > 1/16) ){
					isCrossing = true;	
				} else {
					isCrossing = false
				}
				*/
				isCrossing = FeaturesCross.findLinesCrossing(refGenuine.begin, refGenuine.end, testGenuine.begin, testGenuine.end);
				
				
			}
			
			if ((cross == CrossDescriptior.CROSSING  && isCrossing) || (cross == CrossDescriptior.NOT_CROSSING  && !isCrossing)){
				result = true;
			} else {
				result = false;
			}
			
			return result;
			
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkSize(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{
			
			var size:Number = param;
			
			if (size & SizeDescriptor.UNKNOWN){
				return true;
			}
			
			var result:Boolean = true;
			
			var testRect:Rectangle;
			
			var testSize:Number;
			var refSize:Number;
			
			var divider:Number = 1;
			var ratio:Number;
			
			if (size & SizeDescriptor.HALF){
				divider = 2;
			} else if (size & SizeDescriptor.QUARTER){
				divider = 4;
			} 
			
			if (size & SizeDescriptor.WIDTH){
				refSize  = ref.field.width;
			} else if (size & SizeDescriptor.HEIGHT){
				refSize  = ref.field.height;
			} else /* if (size & SizeDescriptor.LENGTH)*/{
				
				var refGenuine:Primitive = ref as Primitive;
				refSize  = refGenuine.length * Parameters.INTERP_VECTOR_LENGTH;
				
			} 
			
			if (size & SizeDescriptor.THAN_WIDTH){
				testSize = test.field.width;
			} else if (size & SizeDescriptor.THAN_HEIGHT){
				testSize = test.field.height;
			} else if (size & SizeDescriptor.THAN_LENGTH){
				var testGenuine2:Primitive = test as Primitive;
				testSize = testGenuine2.length;
			} else {
				
				if (size & SizeDescriptor.WIDTH){
					testSize = test.field.width;
				} else if (size & SizeDescriptor.HEIGHT){
					testSize = test.field.height;
				} else /* if (size & SizeDescriptor.LENGTH)*/{
					var testGenuine:Primitive = test as Primitive;
					testSize = testGenuine.length * Parameters.INTERP_VECTOR_LENGTH;
					
				} 
			}
			
			testSize /= divider;
			
			if (size & SizeDescriptor.LONGER){
				result = (refSize > testSize);
			} else if (size & SizeDescriptor.SHORTER){
				result = (refSize < testSize);
			} else if (size & SizeDescriptor.ABOUT_15){
				ratio = refSize/testSize;
				if (ratio < 1){
					ratio = 1 / ratio;
				}
				result = (ratio < 1.15);
			} else /* if (size & SizeDescriptor.ABOUT_25) */{
				ratio = refSize/testSize;
				if (ratio < 1){
					ratio = 1 / ratio;
				}
				result = (ratio < 1.25);
			} 
			
			return result;
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkAngle(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{
			
			var angle:Number = param;
			
			if (angle == AngleDescriptor.UNKNOWN  || !(angle & AngleDescriptor.ENUM_MASK)){
				return true;
			}
			
			var refGenuine:Primitive = ref as Primitive;
			var testGenuine:Primitive = test as Primitive;
			
			if (refGenuine == null  ||  testGenuine == null){
				// the relation description is wrong
				return true;
			}
			
			var testAngle:Number
			var refAngle:Number;
			
			if (refObject == ObjectDescriptor.BEGIN){
				refAngle = refGenuine.angleBegin;
			} else if (refObject == ObjectDescriptor.END){
				refAngle = refGenuine.angleEnd;
			} else {
				refAngle = refGenuine.angle;
			}
			
			if (testObject == ObjectDescriptor.BEGIN){
				testAngle = testGenuine.angleBegin;
			} else if (testObject == ObjectDescriptor.END){
				testAngle = testGenuine.angleEnd;
			} else {
				testAngle = testGenuine.angle;
			}
			
			var angleDifference:Number = Angle.differenceAbs90(refAngle, testAngle);
			
			var result:Boolean = true;	
			
			var content:Number = (angle & AngleDescriptor.ANGLE_VALUE_MASK);
			
			if (angle & AngleDescriptor.IS_NARROWER){
				result = (angleDifference < content);
			} else if (angle & AngleDescriptor.IS_WIDER){
				result = (angleDifference > content);
			} else if (angle & AngleDescriptor.IS_ABOUT_MARGIN_22){
				result = (Math.abs(angleDifference - content) < 22.5);
			} else if (angle & AngleDescriptor.IS_ABOUT_MARGIN_30){
				result = (Math.abs(angleDifference - content) < 30);
			} else if (angle & AngleDescriptor.IS_ABOUT_MARGIN_45){
				result = (Math.abs(angleDifference - content) < 45);
			}  
			
			return result;
			
		}
		
		//-------------------------------------------------------------------- 
		
		private function checkDistance(ref:PrimitiveStructure, test:PrimitiveStructure):Boolean{
			
			var distance:Number = param;
			
			var result:Boolean = true;
			
			var distanceValue:Number;
			var referenceField:Rectangle;
			var referenceValue:Number;
			var divider:Number;
			
			var refPoint:Point;
			var testPoint:Point;
			
			if (distance & DistanceDescriptor.UNKNOWN){
				return true;
			}
			
			// find tested points
			
			refPoint = findObjectPoint(ref, refObject);
			testPoint = findObjectPoint(test, testObject);
			
			distanceValue = Point.distance(refPoint, testPoint);
			
			if (distance & DistanceDescriptor.HALF){
				divider = 2;
			} else if (distance & DistanceDescriptor.QUARTER){
				divider = 4;
			} else if (distance & DistanceDescriptor.TWICE){
				divider = 0.5;
			} else {
				divider = 1;
			} 
			
			if (distance & DistanceDescriptor.REF_PRIMITIVE){
				referenceField = ref.field.clone();	
			} else if (distance & DistanceDescriptor.TEST_PRIMITIVE){
				referenceField = test.field.clone();	
			}
			
			if (distance & DistanceDescriptor.WIDTH){
				referenceValue = referenceField.width;
			} else if (distance & DistanceDescriptor.HEIGHT){
				referenceValue = referenceField.height;
			} 
			
			referenceValue /= divider;
			
			if (distance & DistanceDescriptor.IS_LONGER){
				if (distanceValue > referenceValue){
					result = true	
				} else {
					result = false;
				}
			} else if (distance & DistanceDescriptor.IS_SHORTER){
				if (distanceValue < referenceValue){
					result = true	
				} else {
					result = false;
				}
			}
			
			return true;				
		}
			
	
		//-------------------------------------------------------------------- 
		
			
	}
}